home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / sunbird / js / calCalendarManager.js < prev    next >
Encoding:
Text File  |  2007-05-23  |  25.9 KB  |  693 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Oracle Corporation code.
  16.  *
  17.  * The Initial Developer of the Original Code is Oracle Corporation
  18.  * Portions created by the Initial Developer are Copyright (C) 2005
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Stuart Parmenter <stuart.parmenter@oracle.com>
  23.  *   Matthew Willis <lilmatt@mozilla.com>
  24.  *   Michiel van Leeuwen <mvl@exedo.nl>
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. const SUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
  41.  
  42. const kStorageServiceContractID = "@mozilla.org/storage/service;1";
  43. const kStorageServiceIID = Components.interfaces.mozIStorageService;
  44.  
  45. const kMozStorageStatementWrapperContractID = "@mozilla.org/storage/statement-wrapper;1";
  46. const kMozStorageStatementWrapperIID = Components.interfaces.mozIStorageStatementWrapper;
  47. var MozStorageStatementWrapper = null;
  48.  
  49. function createStatement (dbconn, sql) {
  50.     var stmt = dbconn.createStatement(sql);
  51.     var wrapper = MozStorageStatementWrapper();
  52.     wrapper.initialize(stmt);
  53.     return wrapper;
  54. }
  55.  
  56. function onCalCalendarManagerLoad() {
  57.     MozStorageStatementWrapper = new Components.Constructor(kMozStorageStatementWrapperContractID, kMozStorageStatementWrapperIID);
  58. }
  59.  
  60. function calCalendarManager() {
  61.     this.wrappedJSObject = this;
  62.     this.initDB();
  63.     this.mCache = {};
  64.     this.mRefreshTimer = null;
  65.     this.setUpPrefObservers();
  66.     this.setUpReadOnlyObservers();
  67.     this.setUpRefreshTimer();
  68. }
  69.  
  70. function makeURI(uriString)
  71. {
  72.     var ioservice = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
  73.     return ioservice.newURI(uriString, null, null);
  74. }
  75.  
  76. var calCalendarManagerClassInfo = {
  77.     getInterfaces: function (count) {
  78.         var ifaces = [
  79.             Components.interfaces.nsISupports,
  80.             Components.interfaces.calICalendarManager,
  81.             Components.interfaces.nsIObserver,
  82.             Components.interfaces.nsIClassInfo
  83.         ];
  84.         count.value = ifaces.length;
  85.         return ifaces;
  86.     },
  87.  
  88.     getHelperForLanguage: function (language) {
  89.         return null;
  90.     },
  91.  
  92.     contractID: "@mozilla.org/calendar/manager;1",
  93.     classDescription: "Calendar Manager",
  94.     classID: Components.ID("{f42585e7-e736-4600-985d-9624c1c51992}"),
  95.     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
  96.     flags: 0
  97. };
  98.  
  99. calCalendarManager.prototype = {
  100.     QueryInterface: function (aIID) {
  101.         if (aIID.equals(Components.interfaces.nsIClassInfo))
  102.             return calCalendarManagerClassInfo;
  103.  
  104.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  105.             !aIID.equals(Components.interfaces.calICalendarManager) &&
  106.             !aIID.equals(Components.interfaces.nsIObserver))
  107.         {
  108.             throw Components.results.NS_ERROR_NO_INTERFACE;
  109.         }
  110.  
  111.         return this;
  112.     },
  113.  
  114.     setUpPrefObservers: function ccm_setUpPrefObservers() {
  115.         var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
  116.                                 .getService(Components.interfaces.nsIPrefBranch2);
  117.         prefBranch.addObserver("calendar.autorefresh.enabled", this, false);
  118.         prefBranch.addObserver("calendar.autorefresh.timeout", this, false);
  119.     },
  120.  
  121.     // When a calendar fails, its onError doesn't point back to the calendar.
  122.     // Therefore, we add a new announcer for each calendar to tell the user that
  123.     // a specific calendar has failed.  The calendar itself is responsible for
  124.     // putting itself in readonly mode.
  125.     setUpReadOnlyObservers: function() {
  126.         var calendars = this.getCalendars({});
  127.         for each(calendar in calendars) {
  128.             var newObserver = new errorAnnouncer(calendar);
  129.             calendar.addObserver(newObserver.observer);
  130.         }
  131.     },
  132.  
  133.     setUpRefreshTimer: function ccm_setUpRefreshTimer() {
  134.         if (this.mRefreshTimer) {
  135.             this.mRefreshTimer.cancel();
  136.         }
  137.  
  138.         var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
  139.                                 .getService(Components.interfaces.nsIPrefBranch);
  140.  
  141.         var refreshEnabled = false;
  142.         try {
  143.             var refreshEnabled = prefBranch.getBoolPref("calendar.autorefresh.enabled");
  144.         } catch (e) {
  145.         }
  146.  
  147.         // Read and convert the minute-based pref to msecs
  148.         var refreshTimeout = 0;
  149.         try {
  150.             var refreshTimeout = prefBranch.getIntPref("calendar.autorefresh.timeout") * 60000;
  151.         } catch (e) {
  152.         }
  153.  
  154.         if (refreshEnabled && refreshTimeout > 0) {
  155.             this.mRefreshTimer = Components.classes["@mozilla.org/timer;1"]
  156.                                     .createInstance(Components.interfaces.nsITimer);
  157.             this.mRefreshTimer.init(this, refreshTimeout, 
  158.                                    Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
  159.         }
  160.     },
  161.     
  162.     observe: function ccm_observe(aSubject, aTopic, aData) {
  163.         if (aTopic == 'timer-callback') {
  164.             // Refresh all the calendars that can be refreshed.
  165.             var cals = this.getCalendars({});
  166.             for each (var cal in cals) {
  167.                 if (cal.canRefresh) {
  168.                     cal.refresh();
  169.                 }
  170.             }
  171.         } else if (aTopic == 'nsPref:changed') {
  172.             if (aData == "calendar.autorefresh.enabled" ||
  173.                 aData == "calendar.autorefresh.timeout") {
  174.                 this.setUpRefreshTimer();
  175.             }
  176.         }
  177.     },
  178.     
  179.     DB_SCHEMA_VERSION: 7,
  180.  
  181.     upgradeDB: function (oldVersion) {
  182.         // some common helpers
  183.         function addColumn(db, tableName, colName, colType) {
  184.             db.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
  185.         }
  186.  
  187.         if (oldVersion <= 5 && this.DB_SCHEMA_VERSION >= 6) {
  188.             dump ("**** Upgrading calCalendarManager schema to 6\n");
  189.  
  190.             this.mDB.beginTransaction();
  191.             try {
  192.                 // Schema changes in v6:
  193.                 //
  194.                 // - Change all STRING columns to TEXT to avoid SQLite's
  195.                 //   "feature" where it will automatically convert strings to
  196.                 //   numbers (ex: 10e4 -> 10000). See bug 333688.
  197.  
  198.                 // Create the new tables.
  199.  
  200.                 try { 
  201.                     this.mDB.executeSimpleSQL("DROP TABLE cal_calendars_v6;" +
  202.                                               "DROP TABLE cal_calendars_prefs_v6;");
  203.                 } catch (e) {
  204.                     // We should get exceptions for trying to drop tables
  205.                     // that don't (shouldn't) exist.
  206.                 }
  207.  
  208.                 this.mDB.executeSimpleSQL("CREATE TABLE cal_calendars_v6 " +
  209.                                           "(id   INTEGER PRIMARY KEY," +
  210.                                           " type TEXT," +
  211.                                           " uri  TEXT);");
  212.  
  213.                 this.mDB.executeSimpleSQL("CREATE TABLE cal_calendars_prefs_v6 " +
  214.                                           "(id       INTEGER PRIMARY KEY," +
  215.                                           " calendar INTEGER," +
  216.                                           " name     TEXT," +
  217.                                           " value    TEXT);");
  218.  
  219.                 // Copy in the data.
  220.                 var calendarCols = ["id", "type", "uri"];
  221.                 var calendarPrefsCols = ["id", "calendar", "name", "value"];
  222.  
  223.                 this.mDB.executeSimpleSQL("INSERT INTO cal_calendars_v6(" + calendarCols.join(",") + ") " +
  224.                                           "     SELECT " + calendarCols.join(",") +
  225.                                           "       FROM cal_calendars");
  226.  
  227.                 this.mDB.executeSimpleSQL("INSERT INTO cal_calendars_prefs_v6(" + calendarPrefsCols.join(",") + ") " +
  228.                                           "     SELECT " + calendarPrefsCols.join(",") +
  229.                                           "       FROM cal_calendars_prefs");
  230.  
  231.  
  232.                 // Delete each old table and rename the new ones to use the
  233.                 // old tables' names.
  234.                 var tableNames = ["cal_calendars", "cal_calendars_prefs"];
  235.  
  236.                 for (var i in tableNames) {
  237.                     this.mDB.executeSimpleSQL("DROP TABLE " + tableNames[i] + ";" +
  238.                                               "ALTER TABLE " + tableNames[i] + "_v6 " + 
  239.                                               "  RENAME TO " + tableNames[i] + ";");
  240.                 }
  241.  
  242.                 this.mDB.commitTransaction();
  243.                 oldVersion = 6;
  244.             } catch (e) {
  245.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  246.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  247.                                              this.mDB.lastErrorString);
  248.                 this.mDB.rollbackTransaction();
  249.                 throw e;
  250.             }
  251.         }
  252.  
  253.         if (oldVersion != 6) {
  254.             dump ("#######!!!!! calCalendarManager Schema Update failed! " +
  255.                   " db version: " + oldVersion + 
  256.                   " this version: " + this.DB_SCHEMA_VERSION + "\n");
  257.             throw Components.results.NS_ERROR_FAILURE;
  258.         }
  259.     },
  260.  
  261.     initDB: function() {
  262.         var dbService = Components.classes[kStorageServiceContractID]
  263.                                   .getService(kStorageServiceIID);
  264.  
  265.         if ("getProfileStorage" in dbService) {
  266.             // 1.8 branch
  267.             this.mDB = dbService.getProfileStorage("profile");
  268.         } else {
  269.             // trunk
  270.             this.mDB = dbService.openSpecialDatabase("profile");
  271.         }
  272.  
  273.         var sqlTables = { cal_calendars: "id INTEGER PRIMARY KEY, type TEXT, uri TEXT",
  274.                           cal_calendars_prefs: "id INTEGER PRIMARY KEY, calendar INTEGER, name TEXT, value TEXT"
  275.                         };
  276.  
  277.         // Should we check the schema version to see if we need to upgrade?
  278.         var checkSchema = true;
  279.  
  280.         for (table in sqlTables) {
  281.             if (!this.mDB.tableExists(table)) {
  282.                 this.mDB.createTable(table, sqlTables[table]);
  283.                 checkSchema = false;
  284.             }
  285.         }
  286.  
  287.         if (checkSchema) {
  288.             // Check if we need to upgrade
  289.             var version = this.getSchemaVersion();
  290.             //dump ("*** Calendar schema version is: " + version + "\n");
  291.  
  292.             if (version < this.DB_SCHEMA_VERSION) {
  293.                 this.upgradeDB(version);
  294.             } else if (version > this.DB_SCHEMA_VERSION) {
  295.                 // Schema version is newer than what we know how to deal with.
  296.                 // Alert the user, and quit the app.
  297.                 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  298.                                     .getService(Components.interfaces.nsIStringBundleService);
  299.  
  300.                 var brandSb = sbs.createBundle("chrome://branding/locale/brand.properties");
  301.                 var calSb = sbs.createBundle("chrome://calendar/locale/calendar.properties");
  302.  
  303.                 var hostAppName = brandSb.GetStringFromName("brandShortName");
  304.  
  305.                 // If we're Lightning, we want to include the extension name
  306.                 // in the error message rather than blaming Thunderbird.
  307.                 var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
  308.                                         .getService(Ci.nsIXULAppInfo);
  309.                 var calAppName;
  310.                 if (appInfo.ID == SUNBIRD_UID) {
  311.                     calAppName = hostAppName;
  312.                 } else {
  313.                     lightningSb = sbs.createBundle("chrome://lightning/locale/lightning.properties");
  314.                     calAppName = lightningSb.GetStringFromName("brandShortName");
  315.                 }
  316.  
  317.                 var promptSvc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  318.                                           .getService(Components.interfaces.nsIPromptService);
  319.  
  320.                 buttonFlags = promptSvc.BUTTON_POS_0 *
  321.                               promptSvc.BUTTON_TITLE_IS_STRING +
  322.                               promptSvc.BUTTON_POS_0_DEFAULT;
  323.  
  324.                 var choice = promptSvc.confirmEx(
  325.                                 null,
  326.                                 calSb.formatStringFromName("tooNewSchemaErrorBoxTitle",
  327.                                                            [calAppName], 1),
  328.                                 calSb.formatStringFromName("tooNewSchemaErrorBoxText",
  329.                                                            [calAppName, hostAppName], 2),
  330.                                 buttonFlags,
  331.                                 calSb.formatStringFromName("tooNewSchemaButtonQuit",
  332.                                                            [hostAppName], 1),
  333.                                 null, // No second button text
  334.                                 null, // No third button text
  335.                                 null, // No checkbox
  336.                                 {value: false}); // Unnecessary checkbox state
  337.  
  338.                 if (choice == 0) {
  339.                     var startup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
  340.                                             .getService(Components.interfaces.nsIAppStartup);
  341.                     startup.quit(Components.interfaces.nsIAppStartup.eForceQuit);
  342.                 }
  343.             }
  344.         }
  345.  
  346.         this.mSelectCalendars = createStatement (
  347.             this.mDB,
  348.             "SELECT oid,* FROM cal_calendars");
  349.  
  350.         this.mRegisterCalendar = createStatement (
  351.             this.mDB,
  352.             "INSERT INTO cal_calendars (type, uri) " +
  353.             "VALUES (:type, :uri)"
  354.             );
  355.  
  356.         this.mUnregisterCalendar = createStatement (
  357.             this.mDB,
  358.             "DELETE FROM cal_calendars WHERE id = :id"
  359.             );
  360.  
  361.         this.mGetPref = createStatement (
  362.             this.mDB,
  363.             "SELECT value FROM cal_calendars_prefs WHERE calendar = :calendar AND name = :name");
  364.  
  365.         this.mDeletePref = createStatement (
  366.             this.mDB,
  367.             "DELETE FROM cal_calendars_prefs WHERE calendar = :calendar AND name = :name");
  368.  
  369.         this.mInsertPref = createStatement (
  370.             this.mDB,
  371.             "INSERT INTO cal_calendars_prefs (calendar, name, value) " +
  372.             "VALUES (:calendar, :name, :value)");
  373.  
  374.         this.mDeletePrefs = createStatement (
  375.             this.mDB,
  376.             "DELETE FROM cal_calendars_prefs WHERE calendar = :calendar");
  377.     },
  378.  
  379.     /** 
  380.      * @return      db schema version
  381.      * @exception   various, depending on error
  382.      */
  383.     getSchemaVersion: function calMgrGetSchemaVersion() {
  384.         var stmt;
  385.         var version = null;
  386.  
  387.         try {
  388.             stmt = createStatement(this.mDB,
  389.                     "SELECT version FROM cal_calendar_schema_version LIMIT 1");
  390.             if (stmt.step()) {
  391.                 version = stmt.row.version;
  392.             }
  393.             stmt.reset();
  394.  
  395.             if (version !== null) {
  396.                 // This is the only place to leave this function gracefully.
  397.                 return version;
  398.             }
  399.         } catch (e) {
  400.             if (stmt) {
  401.                 stmt.reset();
  402.             }
  403.             dump ("++++++++++++ calMgrGetSchemaVersion() error: " +
  404.                   this.mDB.lastErrorString + "\n");
  405.             Components.utils.reportError("Error getting calendar " +
  406.                                          "schema version! DB Error: " + 
  407.                                          this.mDB.lastErrorString);
  408.             throw e;
  409.         }
  410.  
  411.         throw "cal_calendar_schema_version SELECT returned no results";
  412.     },
  413.  
  414.     notifyObservers: function(functionName, args) {
  415.         function notify(obs) {
  416.             try { obs[functionName].apply(obs, args);  }
  417.             catch (e) { }
  418.         }
  419.         this.mObservers.forEach(notify);
  420.     },
  421.  
  422.     /**
  423.      * calICalendarManager interface
  424.      */
  425.     createCalendar: function(type, uri) {
  426.         var calendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + type].createInstance(Components.interfaces.calICalendar);
  427.         calendar.uri = uri;
  428.         return calendar;
  429.     },
  430.  
  431.     registerCalendar: function(calendar) {
  432.         // bail if this calendar (or one that looks identical to it) is already registered
  433.         if (calendar.id > 0) {
  434.             dump ("registerCalendar: calendar already registered\n");
  435.             throw Components.results.NS_ERROR_FAILURE;
  436.         }
  437.  
  438.         // Add an observer to track readonly-mode triggers
  439.         var newObserver = new errorAnnouncer(calendar);
  440.         calendar.addObserver(newObserver.observer);
  441.  
  442.         var pp = this.mRegisterCalendar.params;
  443.         pp.type = calendar.type;
  444.         pp.uri = calendar.uri.spec;
  445.  
  446.         this.mRegisterCalendar.step();
  447.         this.mRegisterCalendar.reset();
  448.         
  449.         calendar.id = this.mDB.lastInsertRowID;
  450.         
  451.         //dump("adding [" + this.mDB.lastInsertRowID + "]\n");
  452.         //this.mCache[this.mDB.lastInsertRowID] = calendar;
  453.         this.mCache[calendar.id] = calendar;
  454.  
  455.         this.notifyObservers("onCalendarRegistered", [calendar]);
  456.     },
  457.  
  458.     unregisterCalendar: function(calendar) {
  459.         this.notifyObservers("onCalendarUnregistering", [calendar]);
  460.  
  461.         var calendarID = calendar.id;
  462.  
  463.         var pp = this.mUnregisterCalendar.params;
  464.         pp.id = calendarID;
  465.         this.mUnregisterCalendar.step();
  466.         this.mUnregisterCalendar.reset();
  467.  
  468.         // delete prefs for the calendar too
  469.         pp = this.mDeletePrefs.params;
  470.         pp.calendar = calendarID;
  471.         this.mDeletePrefs.step();
  472.         this.mDeletePrefs.reset();
  473.  
  474.         delete this.mCache[calendarID];
  475.     },
  476.  
  477.     deleteCalendar: function(calendar) {
  478.         /* check to see if calendar is unregistered first... */
  479.         /* delete the calendar for good */
  480.         if (calendar.id in this.mCache) {
  481.             throw "Can't delete a registered calendar";
  482.         }
  483.         this.notifyObservers("onCalendarDeleting", [calendar]);
  484.  
  485.         // XXX This is a workaround for bug 351499. We should remove it once
  486.         // we sort out the whole "delete" vs. "unsubscribe" UI thing.
  487.         //
  488.         // We only want to delete the contents of calendars from local
  489.         // providers (storage and memory). Otherwise we may nuke someone's
  490.         // calendar stored on a server when all they really wanted to do was
  491.         // unsubscribe.
  492.         if (calendar instanceof Components.interfaces.calICalendarProvider &&
  493.            (calendar.type == "storage" || calendar.type == "memory")) {
  494.             try {
  495.                 calendar.deleteCalendar(calendar, null);
  496.             } catch (e) {
  497.                 Components.utils.reportError("error purging calendar: " + e);
  498.             }
  499.         }
  500.     },
  501.  
  502.     getCalendars: function(count) {
  503.         var calendars = [];
  504.  
  505.         var stmt = this.mSelectCalendars;
  506.         stmt.reset();
  507.  
  508.         var newCalendarData = [];
  509.  
  510.         while (stmt.step()) {
  511.             var id = stmt.row.id;
  512.             if (!(id in this.mCache)) {
  513.                 newCalendarData.push ({id: id, type: stmt.row.type, uri: stmt.row.uri });
  514.             }
  515.         }
  516.         stmt.reset();
  517.  
  518.         for each (var caldata in newCalendarData) {
  519.             try {
  520.                 var cal = this.createCalendar(caldata.type, makeURI(caldata.uri));
  521.                 cal.id = caldata.id;
  522.                 this.mCache[caldata.id] = cal;
  523.             } catch (e) {
  524.                 dump("Can't create calendar for " + caldata.id + " (" + caldata.type + ", " + 
  525.                      caldata.uri + "): " + e + "\n");
  526.                 continue;
  527.             }
  528.         }
  529.  
  530.         for each (var cal in this.mCache)
  531.             calendars.push (cal);
  532.  
  533.         count.value = calendars.length;
  534.         return calendars;
  535.     },
  536.  
  537.     getCalendarPref: function(calendar, name) {
  538.         // pref names must be lower case
  539.         name = name.toLowerCase();
  540.  
  541.         var stmt = this.mGetPref;
  542.         stmt.reset();
  543.         var pp = stmt.params;
  544.         pp.calendar = calendar.id;
  545.         pp.name = name;
  546.  
  547.         var value = null;
  548.         if (stmt.step()) {
  549.             value = stmt.row.value;
  550.         }
  551.         stmt.reset();
  552.         return value;
  553.     },
  554.  
  555.     setCalendarPref: function(calendar, name, value) {
  556.         // pref names must be lower case
  557.         name = name.toLowerCase();
  558.  
  559.         var calendarID = calendar.id;
  560.  
  561.         this.mDB.beginTransaction();
  562.  
  563.         var pp = this.mDeletePref.params;
  564.         pp.calendar = calendarID;
  565.         pp.name = name;
  566.         this.mDeletePref.step();
  567.         this.mDeletePref.reset();
  568.  
  569.         pp = this.mInsertPref.params;
  570.         pp.calendar = calendarID;
  571.         pp.name = name;
  572.         pp.value = value;
  573.         this.mInsertPref.step();
  574.         this.mInsertPref.reset();
  575.  
  576.         this.mDB.commitTransaction();
  577.  
  578.         this.notifyObservers("onCalendarPrefSet", [calendar, name, value])
  579.     },
  580.  
  581.     deleteCalendarPref: function(calendar, name) {
  582.         // pref names must be lower case
  583.         name = name.toLowerCase();
  584.  
  585.         this.notifyObservers("onCalendarPrefDeleting", [calendar, name]);
  586.  
  587.         var calendarID = calendar.id;
  588.  
  589.         var pp = this.mDeletePref.params;
  590.         pp.calendar = calendarID;
  591.         pp.name = name;
  592.         this.mDeletePref.step();
  593.         this.mDeletePref.reset();
  594.     },
  595.     
  596.     mObservers: Array(),
  597.     addObserver: function(aObserver) {
  598.         if (this.mObservers.indexOf(aObserver) != -1)
  599.             return;
  600.  
  601.         this.mObservers.push(aObserver);
  602.     },
  603.  
  604.     removeObserver: function(aObserver) {
  605.         function notThis(v) {
  606.             return v != aObserver;
  607.         }
  608.         
  609.         this.mObservers = this.mObservers.filter(notThis);
  610.     }
  611. };
  612.  
  613. // This is a prototype object for announcing the fact that a calendar error has
  614. // happened and that the calendar has therefore been put in readOnly mode.  We
  615. // implement a new one of these for each calendar registered to the calmgr.
  616. function errorAnnouncer(calendar) {
  617.     this.calendar = calendar;
  618.     // We compare this to determine if the state actually changed.
  619.     this.storedReadOnly = calendar.readOnly;
  620.     var announcer = this;
  621.     this.observer = {
  622.         // calIObserver:
  623.         onStartBatch: function() {},
  624.         onEndBatch: function() {},
  625.         onLoad: function() {},
  626.         onAddItem: function(aItem) {},
  627.         onModifyItem: function(aNewItem, aOldItem) {},
  628.         onDeleteItem: function(aDeletedItem) {},
  629.         onError: function(aErrNo, aMessage) {
  630.             announcer.announceError(aErrNo, aMessage);
  631.         }
  632.     }
  633. }
  634.  
  635. errorAnnouncer.prototype.announceError = function(aErrNo, aMessage) {
  636.     var paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  637.                                .createInstance(Components.interfaces.nsIDialogParamBlock);
  638.     var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  639.                         .getService(Components.interfaces.nsIStringBundleService);
  640.     var props = sbs.createBundle("chrome://calendar/locale/calendar.properties");
  641.     var errMsg;
  642.     paramBlock.SetNumberStrings(3);
  643.     if (!this.storedReadOnly && this.calendar.readOnly) {
  644.         // Major errors change the calendar to readOnly
  645.         errMsg = props.formatStringFromName("readOnlyMode", [this.calendar.name], 1);
  646.     } else if (!this.storedReadOnly && !this.calendar.readOnly) {
  647.         // Minor errors don't, but still tell the user something went wrong
  648.         errMsg = props.formatStringFromName("minorError", [this.calendar.name], 1);
  649.     } else {
  650.         // The calendar was already in readOnly mode, but still tell the user
  651.         errMsg = props.formatStringFromName("stillReadOnlyError", [this.calendar.name], 1);
  652.     }
  653.  
  654.     // When possible, change the error number into its name, to
  655.     // make it slightly more readable.
  656.     var errCode = "0x"+aErrNo.toString(16);
  657.     const calIErrors = Components.interfaces.calIErrors;
  658.     // Check if it is worth enumerating all the error codes.
  659.     if (aErrNo & calIErrors.ERROR_BASE) {
  660.         for (var err in calIErrors) {
  661.             if (calIErrors[err] == aErrNo) {
  662.                 errCode = err;
  663.             }
  664.         }
  665.     }
  666.  
  667.     var message;    
  668.     switch (aErrNo) {
  669.         case calIErrors.CAL_UTF8_DECODING_FAILED:
  670.             message = props.GetStringFromName("utf8DecodeError");
  671.             break;
  672.         case calIErrors.ICS_MALFORMEDDATA:
  673.             message = props.GetStringFromName("icsMalformedError");
  674.             break;
  675.         default:
  676.             message = aMessage
  677.     }
  678.  
  679.     paramBlock.SetString(0, errMsg);
  680.     paramBlock.SetString(1, errCode);
  681.     paramBlock.SetString(2, message);
  682.  
  683.     this.storedReadOnly = this.calendar.readOnly;
  684.  
  685.     var wWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  686.                              .getService(Components.interfaces.nsIWindowWatcher);
  687.     wWatcher.openWindow(null,
  688.                         "chrome://calendar/content/calErrorPrompt.xul",
  689.                         "_blank",
  690.                         "chrome,dialog=yes",
  691.                         paramBlock);
  692. }
  693.